<?php

namespace Plugin\EasyCVR\Service;

use Common\Common;
use Common\HttpClient;
use GuzzleHttp\Exception\GuzzleException;
use Hyperf\Collection\Arr;
use Plugin\EasyCVR\Exception\EasyCVRException;
use Psr\Log\LogLevel;

class EasyCVRService
{
    private string $token = '';
    private string $url = '';
    private ?HttpClient $httpClient = null;

    public function __construct()
    {
        $config = Common::getConfigByGroup('EasyCVR');
        if (!$config) {
            return;
        }
        if (!Arr::get($config, 'username.value') || !Arr::get($config, 'password.value') || !Arr::get($config, 'url.value')) {
            return;
        }
        $userName = $config['username']['value'];
        $password = $config['password']['value'];
        $this->url = $config['url']['value'];
        $this->httpClient = new HttpClient([
            'base_uri' => $this->url,
            'timeout' => 5,
        ]);
        $this->token = $this->getToken($userName, $password);
    }

    /**
     * 获取token
     * @param string $userName
     * @param string $password
     * @return string
     * @throws EasyCVRException
     * @throws GuzzleException
     */
    private function getToken(string $userName, string $password): string
    {
        if (!$this->token) {
            $content = $this->login($userName, $password);
            $this->token = $content['Token'];
        }
        return $this->token;
    }

    /**
     * 登录
     * @param string $userName
     * @param string $password
     * @return array
     * @throws EasyCVRException
     * @throws GuzzleException
     */
    private function login(string $userName, string $password): array
    {
        // 必需参数校验
        if (empty($userName)) {
            throw new EasyCVRException('用户名不能为空');
        }
        if (empty($password)) {
            throw new EasyCVRException('密码不能为空');
        }

        $res = $this->httpClient->get('/api/v1/login', [
            'query' => [
                'username' => $userName,
                'password' => md5($password),
            ]
        ]);
        if (isset($res['content']) && $res['content']['EasyDarwin']['Header']['ErrorNum'] == '200') {
            return $res['content']['EasyDarwin']['Body'];
        }
        Common::log(__CLASS__, 'CVR登录失败:' . json_encode($res), LogLevel::WARNING);
        throw new EasyCVRException('CVR登录失败');
    }

    /**
     * 登出
     * @return array
     * @throws EasyCVRException
     */
    public function logout(): array
    {
        return $this->request('get', '/api/v1/logout');
    }

    /**
     * 新增角色
     * @param string $name 角色名称
     * @param string $description 角色描述
     * @param array $permissions 权限列表
     * @return array
     * @throws EasyCVRException
     */
    public function addRole(string $name, string $description, array $permissions): array
    {
        // 必需参数校验
        if (empty($name)) {
            throw new EasyCVRException('角色名称不能为空');
        }

        $data = [
            'name' => $name,
            'description' => $description,
        ];

        // 可选参数非null才加入
        if (!empty($permissions)) {
            $data['permissions'] = $permissions;
        }

        return $this->request('postJson', '/api/v1/roles', $data);
    }

    /**
     * 修改角色
     * @param int $roleId 角色ID
     * @param string $name 角色名称
     * @param string $description 角色描述
     * @param array $permissions 权限列表
     * @return array
     * @throws EasyCVRException
     */
    public function updateRole(int $roleId, string $name, string $description, array $permissions = []): array
    {
        // 必需参数校验
        if ($roleId <= 0) {
            throw new EasyCVRException('角色ID必须大于0');
        }
        if (empty($name)) {
            throw new EasyCVRException('角色名称不能为空');
        }

        $data = [
            'name' => $name,
            'description' => $description,
        ];

        // 可选参数非null才加入
        if (!empty($permissions)) {
            $data['permissions'] = $permissions;
        }

        return $this->request('postJson', "/api/v1/roles/$roleId", $data);
    }

    /**
     * 用户列表
     * @param string $start 开始位置
     * @param string $limit 每页数量
     * @param string|null $q 查询关键字
     * @return array
     * @throws EasyCVRException
     */
    public function userList(string $start = '0', string $limit = '50', ?string $q = null): array
    {
        // 必需参数校验
        if (!is_numeric($start)) {
            throw new EasyCVRException('开始位置必须是数字');
        }
        if (!is_numeric($limit)) {
            throw new EasyCVRException('每页数量必须是数字');
        }

        $query = [
            'start' => $start,
            'limit' => $limit,
        ];

        // 可选参数非null才加入
        if ($q !== null) {
            $query['q'] = $q;
        }

        return $this->request('get', '/api/v1/users', [
            'query' => $query
        ]);
    }

    /**
     * 新增用户
     * @param string $username 用户名
     * @param string $password 密码
     * @param string $nickname 昵称
     * @param int $roleId 角色ID
     * @return array
     * @throws EasyCVRException
     */
    public function addUser(string $username, string $password, string $nickname, int $roleId): array
    {
        // 必需参数校验
        if (empty($username)) {
            throw new EasyCVRException('用户名不能为空');
        }
        if (empty($password)) {
            throw new EasyCVRException('密码不能为空');
        }
        if (empty($nickname)) {
            throw new EasyCVRException('昵称不能为空');
        }
        if ($roleId <= 0) {
            throw new EasyCVRException('角色ID必须大于0');
        }

        return $this->request('postJson', '/api/v1/users', [
            'username' => $username,
            'password' => md5($password),
            'nickname' => $nickname,
            'role_id' => $roleId
        ]);
    }

    /**
     * 修改用户
     * @param int $userId 用户ID
     * @param string $username 用户名
     * @param string $nickname 昵称
     * @param int $roleId 角色ID
     * @return array
     * @throws EasyCVRException
     */
    public function updateUser(int $userId, string $username, string $nickname, int $roleId): array
    {
        // 必需参数校验
        if ($userId <= 0) {
            throw new EasyCVRException('用户ID必须大于0');
        }
        if (empty($username)) {
            throw new EasyCVRException('用户名不能为空');
        }
        if (empty($nickname)) {
            throw new EasyCVRException('昵称不能为空');
        }
        if ($roleId <= 0) {
            throw new EasyCVRException('角色ID必须大于0');
        }

        return $this->request('postJson', "/api/v1/users/$userId", [
            'username' => $username,
            'nickname' => $nickname,
            'role_id' => $roleId
        ]);
    }

    /**
     * 角色列表
     * @param string $start 开始位置
     * @param string $limit 每页数量
     * @param string|null $q 查询关键字
     * @return array
     * @throws EasyCVRException
     */
    public function roleList(string $start = '0', string $limit = '50', ?string $q = null): array
    {
        // 必需参数校验
        if (!is_numeric($start)) {
            throw new EasyCVRException('开始位置必须是数字');
        }
        if (!is_numeric($limit)) {
            throw new EasyCVRException('每页数量必须是数字');
        }

        $query = [
            'start' => $start,
            'limit' => $limit,
        ];

        // 可选参数非null才加入
        if ($q !== null) {
            $query['q'] = $q;
        }

        return $this->request('get', '/api/v1/roles', [
            'query' => $query
        ]);
    }

    private function request(string $method, string $uri, array $data = [])
    {
        $requestData = array_merge($data, [
            'headers' => ['Token' => $this->token],
        ]);
        switch ($method) {
            case 'get':
                $res = $this->httpClient->get($uri, $requestData);
                break;
            case 'postForm':
                $res = $this->httpClient->postForm($uri, $requestData);
                break;
            case 'postJson':
                $res = $this->httpClient->postJson($uri, $requestData);
                break;
            default:
                throw new EasyCVRException('不支持的请求方式');
        }
        if (!isset($res['content']) || $res['content']['EasyDarwin']['Header']['ErrorNum'] != '200') {
            throw new EasyCVRException('CVR请求失败');
        }
        return $res['content']['EasyDarwin']['Body'];
    }

//----------------------------------播放相关------------------------------------------

    /**
     * 语音对讲
     * @param string $device 设备ID
     * @param string $channel 通道ID
     * @param string $audioData pcm音频数据 8000 采样率, 单通道
     * @return array
     * @throws EasyCVRException
     */
    public function voiceTalk(string $device, string $channel, string $audioData): array
    {
        // 必需参数校验
        if (empty($device)) {
            throw new EasyCVRException('设备ID不能为空');
        }
        if (empty($channel)) {
            throw new EasyCVRException('通道ID不能为空');
        }
        if (empty($audioData)) {
            throw new EasyCVRException('音频数据不能为空');
        }

        return $this->request('postForm', '/api/v1/voicetalk', [
            'device' => $device,
            'channel' => $channel,
            'audio_data' => $audioData
        ]);
    }

    /**
     * 获取播放地址（保活）
     * @param string $device 设备ID
     * @param string $channel 通道ID
     * @param string $protocol 协议ws_flv、flv、hls、webrtc、fmp4、ws_fmp4、rtsp、rtmp
     * @param string $stream 主码流main;子码流sub
     * @param string|null $requestId
     * @param string|null $playerId
     * @param string|null $reqId
     * @param string|null $type 保活视频流时必传 keepalive
     * @param string|null $url 保活视频流地址
     * @return array
     * @throws EasyCVRException
     */
    public function channelStream(string $device, string $channel, string $protocol, string $stream, ?string $requestId = null, ?string $playerId = null, ?string $reqId = null, ?string $type = null, ?string $url = null): array
    {
        // 必需参数校验
        if (empty($device)) {
            throw new EasyCVRException('设备ID不能为空');
        }
        if (empty($channel)) {
            throw new EasyCVRException('通道ID不能为空');
        }
        if (empty($protocol)) {
            throw new EasyCVRException('协议不能为空');
        }
        if (empty($stream)) {
            throw new EasyCVRException('码流类型不能为空');
        }

        $query = [
            'device' => $device,
            'channel' => $channel,
            'protocol' => $protocol,
            'stream' => $stream,
        ];

        // 可选参数非null才加入
        if ($requestId !== null) {
            $query['request_id'] = $requestId;
        }
        if ($playerId !== null) {
            $query['playerid'] = $playerId;
        }
        if ($reqId !== null) {
            $query['reqid'] = $reqId;
        }
        if ($type !== null) {
            $query['type'] = $type;
        }
        if ($url !== null) {
            $query['url'] = $url;
        }

        return $this->request('get', '/api/v1/devices/channelstream', [
            'query' => $query
        ]);
    }

    /**
     * 获取流地址
     * @param string $device 设备ID
     * @param string $channel 通道ID
     * @return array
     * @throws EasyCVRException
     */
    public function channelStreamAllAddr(string $device, string $channel): array
    {
        // 必需参数校验
        if (empty($device)) {
            throw new EasyCVRException('设备ID不能为空');
        }
        if (empty($channel)) {
            throw new EasyCVRException('通道ID不能为空');
        }

        return $this->request('get', '/api/v1/devices/channelstreamalladdr', [
            'query' => [
                'device' => $device,
                'channel' => $channel,
            ]
        ]);
    }

    /**
     * 获取流信息
     * @param string $url 视频流地址
     * @param string $channel 通道ID
     * @return array
     * @throws EasyCVRException
     */
    public function channelStreamInfo(string $url, string $channel): array
    {
        // 必需参数校验
        if (empty($url)) {
            throw new EasyCVRException('视频流地址不能为空');
        }
        if (empty($channel)) {
            throw new EasyCVRException('通道ID不能为空');
        }

        return $this->request('get', '/api/v1/streamInfo', [
            'query' => [
                'url' => $url,
                'channel' => $channel,
            ]
        ]);
    }

    /**
     * 视频流诊断信息
     * @param string $requestId
     * @return array
     * @throws EasyCVRException
     */
    public function playState(string $requestId): array
    {
        // 必需参数校验
        if (empty($requestId)) {
            throw new EasyCVRException('请求ID不能为空');
        }

        return $this->request('get', '/api/v2/playState', [
            'query' => [
                'request_id' => $requestId,
            ]
        ]);
    }

    /**
     * 获取预置位列表
     * @param string $channel 通道ID
     * @return array
     * @throws EasyCVRException
     */
    public function onvifGetPresetList(string $channel): array
    {
        // 必需参数校验
        if (empty($channel)) {
            throw new EasyCVRException('通道ID不能为空');
        }

        return $this->request('get', '/api/v1/onvifgetpresetlist', [
            'query' => [
                'channel' => $channel,
            ]
        ]);
    }

    /**
     * 预置位跳转
     * @param string $channel 通道ID
     * @param string $presetToken 预置位序列号
     * @return array
     * @throws EasyCVRException
     */
    public function onvifGotoPreset(string $channel, string $presetToken): array
    {
        // 必需参数校验
        if (empty($channel)) {
            throw new EasyCVRException('通道ID不能为空');
        }
        if (empty($presetToken)) {
            throw new EasyCVRException('预置位序列号不能为空');
        }

        return $this->request('get', '/api/v1/onvifgotopreset', [
            'query' => [
                'channel' => $channel,
                'presettoken' => $presetToken,
            ]
        ]);
    }

    /**
     * 删除预置位
     * @param string $channel 通道ID
     * @param string $presetToken 预置位序列号
     * @return array
     * @throws EasyCVRException
     */
    public function onvifRemovePreset(string $channel, string $presetToken): array
    {
        // 必需参数校验
        if (empty($channel)) {
            throw new EasyCVRException('通道ID不能为空');
        }
        if (empty($presetToken)) {
            throw new EasyCVRException('预置位序列号不能为空');
        }

        return $this->request('get', '/api/v1/onvifremovepreset', [
            'query' => [
                'channel' => $channel,
                'presettoken' => $presetToken,
            ]
        ]);
    }

    /**
     * 云台控制
     * @param string $device 设备ID
     * @param string $channel 通道ID
     * @param string $command 动作命令，stop, up, down, left, right, zoomin, zoomout，一次操作结束，需调用停止，否则摄像头在一直按照上一次操作旋转
     * @param string $direction 具体停止那个操作，hksdk时使用到，up, down, left, right
     * @param int $speed 速度
     * @return array
     * @throws EasyCVRException
     */
    public function PTZControl(string $device, string $channel, string $command, string $direction, int $speed): array
    {
        // 必需参数校验
        if (empty($device)) {
            throw new EasyCVRException('设备ID不能为空');
        }
        if (empty($channel)) {
            throw new EasyCVRException('通道ID不能为空');
        }
        if (empty($command)) {
            throw new EasyCVRException('动作命令不能为空');
        }
        if (empty($direction)) {
            throw new EasyCVRException('方向不能为空');
        }
        if ($speed < 0) {
            throw new EasyCVRException('速度不能小于0');
        }

        return $this->request('get', '/api/v1/ptzcontrol', [
            'query' => [
                'device' => $device,
                'channel' => $channel,
                'command' => $command,
                'direction' => $direction,
                'speed' => $speed,
            ]
        ]);
    }

    /**
     * 停止拉取视频流
     * @param string $streamType 视频流类型 play/playback
     * @param string $device 设备ID
     * @param string $channel 通道ID
     * @param string $url 播放地址
     * @return array
     * @throws EasyCVRException
     */
    public function stopChannelStream(string $streamType, string $device, string $channel, string $url): array
    {
        // 必需参数校验
        if (empty($streamType)) {
            throw new EasyCVRException('视频流类型不能为空');
        }
        if (empty($device)) {
            throw new EasyCVRException('设备ID不能为空');
        }
        if (empty($channel)) {
            throw new EasyCVRException('通道ID不能为空');
        }
        if (empty($url)) {
            throw new EasyCVRException('播放地址不能为空');
        }

        return $this->request('get', '/api/v1/devices/stopchannelstream', [
            'query' => [
                'stream_type' => $streamType,
                'device' => $device,
                'channel' => $channel,
                'url' => $url,
            ]
        ]);
    }

//----------------------------------设备管理------------------------------------------

    /**
     * 设备列表
     * @param string $start 开始位置
     * @param string $limit 每页数量
     * @param string|null $q
     * @return array
     * @throws EasyCVRException
     */
    public function devicesConfig(string $start = '0', string $limit = '50', ?string $q = null): array
    {
        // 必需参数校验
        if (!is_numeric($start)) {
            throw new EasyCVRException('开始位置必须是数字');
        }
        if (!is_numeric($limit)) {
            throw new EasyCVRException('每页数量必须是数字');
        }

        $query = [
            'start' => $start,
            'limit' => $limit,
        ];

        // 可选参数非null才加入
        if ($q !== null) {
            $query['q'] = $q;
        }

        return $this->request('get', '/api/v1/devicesconfig', [
            'query' => $query
        ]);
    }

    /**
     * 添加设备
     * @param string $transport 接入协议TCP/UDP
     * @param string $transportMode 接入协议
     * @param string $deviceName 设备名称
     * @param string $smsHost
     * @param string $deviceIp 设备IP
     * @param int $devicePort 设备端口
     * @param string $deviceUserName 设备用户名
     * @param string $devicePassword 设备密码
     * @param string $appKey
     * @param string $secretKey
     * @param string $manufacturer 制造商DAHUA、EasyCVR、ipc、HIKVision
     * @param string $serverID 节点ID：EasyCVR_NODE_01
     * @param string $gatApeID
     * @param string $gatUserID
     * @param string $gatPassword
     * @param string $gatMaxCount
     * @param string $clusterServerID
     * @param string $mediaTransportMode
     * @param bool $enable 启用
     * @return array
     * @throws EasyCVRException
     */
    public function addIPCDevice(
        string $transport,
        string $transportMode,
        string $deviceName,
        string $smsHost,
        string $deviceIp,
        int    $devicePort,
        string $deviceUserName,
        string $devicePassword,
        string $appKey,
        string $secretKey,
        string $manufacturer,
        string $serverID,
        string $gatApeID,
        string $gatUserID,
        string $gatPassword,
        string $gatMaxCount,
        string $clusterServerID,
        string $mediaTransportMode,
        bool   $enable = true
    ): array
    {
        // 必需参数校验
        if (empty($transport)) {
            throw new EasyCVRException('接入协议TCP/UDP不能为空');
        }
        if (empty($transportMode)) {
            throw new EasyCVRException('接入协议不能为空');
        }
        if (empty($deviceName)) {
            throw new EasyCVRException('设备名称不能为空');
        }
        if (empty($deviceIp)) {
            throw new EasyCVRException('设备IP不能为空');
        }
        if ($devicePort <= 0) {
            throw new EasyCVRException('设备端口必须大于0');
        }
        if (empty($manufacturer)) {
            throw new EasyCVRException('制造商不能为空');
        }
        if (empty($serverID)) {
            throw new EasyCVRException('节点ID不能为空');
        }
        if (empty($mediaTransportMode)) {
            throw new EasyCVRException('媒体传输模式不能为空');
        }

        $data = [
            'Transport' => $transport,
            'TransportMode' => $transportMode,
            'DeviceName' => $deviceName,
            'SmsHost' => $smsHost,
            'DeviceType' => 'ipc',
            'DeviceIp' => $deviceIp,
            'DevicePort' => $devicePort,
            'DeviceUserName' => $deviceUserName,
            'DeviceUserPW' => $devicePassword,
            'AppKey' => $appKey,
            'SecretKey' => $secretKey,
            'Manufacturer' => $manufacturer,
            'Enable' => $enable,
            'ServerID' => $serverID,
            'GatApeID' => $gatApeID,
            'GatUserId' => $gatUserID,
            'GatPassword' => $gatPassword,
            'MediaTransportMode' => $mediaTransportMode,
            'GatMaxCount' => $gatMaxCount,
            'ClusterServerID' => $clusterServerID,
        ];

        // 过滤掉null值
        $data = array_filter($data, function($value) {
            return $value !== null;
        });

        return $this->request('postJson', '/api/v1/adddevice', $data);
    }

    /**
     * 添加SDK设备
     * @param string $deviceName 设备名称
     * @param string $deviceIp 设备IP
     * @param int $devicePort 设备端口
     * @param string $deviceUserName 设备用户名
     * @param string $devicePassword 设备密码
     * @param string $manufacturer 制造商DAHUA、EasyCVR、ipc、HIKVision
     * @param string $serverID 节点ID：EasyCVR_NODE_01
     * @param bool $enable 启用
     * @return array
     * @throws EasyCVRException
     */
    public function addSDKDevice(
        string $deviceName,
        string $deviceIp,
        int    $devicePort,
        string $deviceUserName,
        string $devicePassword,
        string $manufacturer,
        string $serverID,
        bool   $enable = true
    ): array
    {
        // 必需参数校验
        if (empty($deviceName)) {
            throw new EasyCVRException('设备名称不能为空');
        }
        if (empty($deviceIp)) {
            throw new EasyCVRException('设备IP不能为空');
        }
        if ($devicePort <= 0) {
            throw new EasyCVRException('设备端口必须大于0');
        }
        if (empty($manufacturer)) {
            throw new EasyCVRException('制造商不能为空');
        }
        if (empty($serverID)) {
            throw new EasyCVRException('节点ID不能为空');
        }

        return $this->request('postJson', '/api/v1/adddevice', [
            'DeviceName' => $deviceName,
            'DeviceType' => 'sdk',
            'DeviceIp' => $deviceIp,
            'DevicePort' => $devicePort,
            'DeviceUserName' => $deviceUserName,
            'DeviceUserPW' => $devicePassword,
            'Manufacturer' => $manufacturer,
            'Enable' => $enable,
            'ServerID' => $serverID
        ]);
    }

    /**
     * 添加RTMP推流设备
     * @param string $deviceName 设备名称
     * @param string $serverID 节点ID：EasyCVR_NODE_01
     * @param bool $enable 启用
     * @return array
     * @throws EasyCVRException
     */
    public function addRTMPPushDevice(
        string $deviceName,
        string $serverID,
        bool   $enable = true
    ): array
    {
        // 必需参数校验
        if (empty($deviceName)) {
            throw new EasyCVRException('设备名称不能为空');
        }
        if (empty($serverID)) {
            throw new EasyCVRException('节点ID不能为空');
        }

        return $this->request('postJson', '/api/v1/adddevice', [
            'DeviceName' => $deviceName,
            'DeviceType' => 'rtmp_push',
            'Enable' => $enable,
            'ServerID' => $serverID
        ]);
    }

    /**
     * 编辑设备
     * @param string $deviceId 设备ID
     * @param string $deviceName 设备名称
     * @param string $transport 传输协议TCP/UDP
     * @param string $deviceType 接入协议
     * @param string $deviceIp 设备IP
     * @param int $devicePort 设备端口
     * @param string $deviceUserName 设备用户名
     * @param string $devicePassword 设备密码
     * @param string $manufacturer 制造商
     * @param bool $enable 启用
     * @param string $serverID 节点ID
     * @return array
     * @throws EasyCVRException
     */
    public function editDevice(
        string $deviceId,
        string $deviceName,
        string $transport,
        string $deviceType,
        string $deviceIp,
        int    $devicePort,
        string $deviceUserName,
        string $devicePassword,
        string $manufacturer,
        bool   $enable,
        string $serverID
    ): array
    {
        // 必需参数校验
        if (empty($deviceId)) {
            throw new EasyCVRException('设备ID不能为空');
        }
        if (empty($deviceName)) {
            throw new EasyCVRException('设备名称不能为空');
        }
        if (empty($transport)) {
            throw new EasyCVRException('传输协议不能为空');
        }
        if (empty($deviceType)) {
            throw new EasyCVRException('接入协议不能为空');
        }
        if (empty($deviceIp)) {
            throw new EasyCVRException('设备IP不能为空');
        }
        if ($devicePort <= 0) {
            throw new EasyCVRException('设备端口必须大于0');
        }
        if (empty($manufacturer)) {
            throw new EasyCVRException('制造商不能为空');
        }
        if (empty($serverID)) {
            throw new EasyCVRException('节点ID不能为空');
        }

        return $this->request('postJson', '/api/v1/editdevice', [
            'DeviceId' => $deviceId,
            'DeviceName' => $deviceName,
            'Transport' => $transport,
            'DeviceType' => $deviceType,
            'DeviceIp' => $deviceIp,
            'DevicePort' => $devicePort,
            'DeviceUserName' => $deviceUserName,
            'DeviceUserPW' => $devicePassword,
            'Manufacturer' => $manufacturer,
            'Enable' => $enable,
            'ServerID' => $serverID
        ]);
    }

    /**
     * 删除设备
     * @param string $device 设备ID
     * @return array
     * @throws EasyCVRException
     */
    public function delDevice(string $device): array
    {
        // 必需参数校验
        if (empty($device)) {
            throw new EasyCVRException('设备ID不能为空');
        }

        return $this->request('postJson', '/api/v1/deldevice', [
            'Device' => $device
        ]);
    }

    /**
     * 通道列表
     * @param string $device 设备ID
     * @param string $start 开始
     * @param string $limit 结束
     * @param string|null $q
     * @return array
     * @throws EasyCVRException
     */
    public function channelsConfig(string $device, string $start = '0', string $limit = '50', ?string $q = null): array
    {
        // 必需参数校验
        if (empty($device)) {
            throw new EasyCVRException('设备ID不能为空');
        }
        if (!is_numeric($start)) {
            throw new EasyCVRException('开始位置必须是数字');
        }
        if (!is_numeric($limit)) {
            throw new EasyCVRException('每页数量必须是数字');
        }

        $query = [
            'device' => $device,
            'start' => $start,
            'limit' => $limit,
        ];

        // 可选参数非null才加入
        if ($q !== null) {
            $query['q'] = $q;
        }

        return $this->request('get', '/api/v1/channelsconfig', [
            'query' => $query
        ]);
    }

    /**
     * 添加通道
     * @param int $parentDeviceID 设备ID
     * @param string $protocol 接入协议RTSP、RTMP、HTTP、ONVIF
     * @param string $name 通道名称
     * @param string $username rtsp接入设备登录用户名
     * @param string $password rtsp接入设备登录密码
     * @param int $enable 是否启用
     * @param string $rtspUrl 主码流地址
     * @param string|null $recordUrl rtmp地址
     * @param int $enableCdn 是否开启CDN
     * @param string|null $cdnUrl CDN地址
     * @param string|null $recordPlan
     * @param int $enableRecord
     * @param int $enableAudio 是否开启音频
     * @param int $onDemand 是否按需直播
     * @param bool $enableH265ToH264 是否开启转码
     * @param int $gpsLongitude GPS纬度
     * @param int $gpsLatitude GPS经度度
     * @param string|null $watermarkingText 水印内容
     * @param string|null $watermarkingPlace 水印显示位置
     * @param int $expires Token时效(秒)
     * @param int $latitude 设备纬度
     * @param int $longitude 设备经度
     * @param int $deviceID 设备ID
     * @return array
     * @throws EasyCVRException
     */
    public function addChannel(
        int    $parentDeviceID,
        string $protocol,
        string $name,
        string $username,
        string $password,
        int    $enable,
        string $rtspUrl,
        ?string $recordUrl = null,
        int    $enableCdn = 0,
        ?string $cdnUrl = null,
        ?string $recordPlan = null,
        int    $enableRecord = 0,
        int    $enableAudio = 0,
        int    $onDemand = 0,
        bool   $enableH265ToH264 = false,
        int    $gpsLongitude = 0,
        int    $gpsLatitude = 0,
        ?string $watermarkingText = null,
        ?string $watermarkingPlace = null,
        int    $expires = 0,
        int    $latitude = 0,
        int    $longitude = 0,
        int    $deviceID = 0
    ): array
    {
        // 必需参数校验
        if ($parentDeviceID <= 0) {
            throw new EasyCVRException('父设备ID必须大于0');
        }
        if (empty($protocol)) {
            throw new EasyCVRException('接入协议不能为空');
        }
        if (empty($name)) {
            throw new EasyCVRException('通道名称不能为空');
        }
        if ($enable < 0 || $enable > 1) {
            throw new EasyCVRException('启用状态必须是0或1');
        }
        if (empty($rtspUrl)) {
            throw new EasyCVRException('主码流地址不能为空');
        }

        $data = [
            'parentDeviceID' => $parentDeviceID,
            'Protocol' => $protocol,
            'Name' => $name,
            'Username' => $username,
            'Password' => $password,
            'Enable' => $enable,
            'RtspUrl' => $rtspUrl,
            'EnableCdn' => $enableCdn,
            'EnableRecord' => $enableRecord,
            'EnableAudio' => $enableAudio,
            'OnDemand' => $onDemand,
            'EnableH265ToH264' => $enableH265ToH264,
            'GpsLongitude' => $gpsLongitude,
            'GpsLatitude' => $gpsLatitude,
            'Expires' => $expires,
            'Latitude' => $latitude,
            'Longitude' => $longitude,
            'DeviceID' => $deviceID
        ];

        // 可选参数非null才加入
        if ($recordUrl !== null) {
            $data['RecordUrl'] = $recordUrl;
        }
        if ($cdnUrl !== null) {
            $data['CdnUrl'] = $cdnUrl;
        }
        if ($recordPlan !== null) {
            $data['RecordPlan'] = $recordPlan;
        }
        if ($watermarkingText !== null) {
            $data['WatermarkingText'] = $watermarkingText;
        }
        if ($watermarkingPlace !== null) {
            $data['WatermarkingPlace'] = $watermarkingPlace;
        }

        return $this->request('postJson', '/api/v1/addchannel', $data);
    }

    /**
     * 编辑通道
     *
     * @param int $channelID
     * @param int $enable
     * @param int $onDemand
     * @param string $name
     * @param string $ip
     * @param int $port
     * @param string $username
     * @param string $password
     * @param string $protocol
     * @param string $rtspUrl
     * @param string $transport
     * @param string $onvif
     * @param int $enableRecord
     * @param string|null $recordUrl
     * @param string|null $recordPlan
     * @param int $enableAudio
     * @param int $enableCdn
     * @param string|null $cdnUrl
     * @param int $channelNum
     * @param int $parentDeviceID
     * @param int $online
     * @param string|null $snapUrl
     * @return array
     * @throws EasyCVRException
     */
    public function editChannel(
        int    $channelID,
        int    $enable,
        int    $onDemand,
        string $name,
        string $ip,
        int    $port,
        string $username,
        string $password,
        string $protocol,
        string $rtspUrl,
        string $transport,
        string $onvif,
        int    $enableRecord,
        ?string $recordUrl = null,
        ?string $recordPlan = null,
        int    $enableAudio = 0,
        int    $enableCdn = 0,
        ?string $cdnUrl = null,
        int    $channelNum = 0,
        int    $parentDeviceID = 0,
        int    $online = 0,
        ?string $snapUrl = null
    ): array
    {
        // 必需参数校验
        if ($channelID <= 0) {
            throw new EasyCVRException('通道ID必须大于0');
        }
        if ($enable < 0 || $enable > 1) {
            throw new EasyCVRException('启用状态必须是0或1');
        }
        if (empty($name)) {
            throw new EasyCVRException('通道名称不能为空');
        }
        if (empty($ip)) {
            throw new EasyCVRException('IP地址不能为空');
        }
        if ($port <= 0) {
            throw new EasyCVRException('端口必须大于0');
        }
        if (empty($protocol)) {
            throw new EasyCVRException('协议不能为空');
        }
        if (empty($rtspUrl)) {
            throw new EasyCVRException('RTSP地址不能为空');
        }
        if (empty($transport)) {
            throw new EasyCVRException('传输方式不能为空');
        }

        $data = [
            'ChannelID' => $channelID,
            'Enable' => $enable,
            'OnDemand' => $onDemand,
            'Name' => $name,
            'IP' => $ip,
            'Port' => $port,
            'Username' => $username,
            'Password' => $password,
            'Protocol' => $protocol,
            'RtspUrl' => $rtspUrl,
            'Transport' => $transport,
            'Onvif' => $onvif,
            'EnableRecord' => $enableRecord,
            'EnableAudio' => $enableAudio,
            'EnableCdn' => $enableCdn,
            'ChannelNum' => $channelNum,
            'ParentDeviceID' => $parentDeviceID,
            'Online' => $online,
        ];

        // 可选参数非null才加入
        if ($recordUrl !== null) {
            $data['RecordUrl'] = $recordUrl;
        }
        if ($recordPlan !== null) {
            $data['RecordPlan'] = $recordPlan;
        }
        if ($cdnUrl !== null) {
            $data['CdnUrl'] = $cdnUrl;
        }
        if ($snapUrl !== null) {
            $data['SnapUrl'] = $snapUrl;
        }

        return $this->request('postJson', '/api/v1/editchannel', $data);
    }

    /**
     * 删除通道
     * @param int $channel
     * @return array
     * @throws EasyCVRException
     */
    public function delChannel(int $channel): array
    {
        // 必需参数校验
        if ($channel <= 0) {
            throw new EasyCVRException('通道ID必须大于0');
        }

        return $this->request('get', '/api/v1/delchannel', [
            'query' => [
                'channel' => $channel
            ]
        ]);
    }

    /**
     * 收藏列表
     * @param string $start 开始位置
     * @param string $limit 每页数量
     * @return array
     * @throws EasyCVRException
     */
    public function favoritesList(string $start = '0', string $limit = '50'): array
    {
        // 必需参数校验
        if (!is_numeric($start)) {
            throw new EasyCVRException('开始位置必须是数字');
        }
        if (!is_numeric($limit)) {
            throw new EasyCVRException('每页数量必须是数字');
        }

        return $this->request('get', '/api/v1/favorites', [
            'query' => [
                'start' => $start,
                'limit' => $limit
            ]
        ]);
    }

//----------------------------------分组管理------------------------------------------

    /**
     * 分组列表
     * @param string $start 开始位置
     * @param string $limit 每页数量
     * @param string|null $q 查询关键字
     * @return array
     * @throws EasyCVRException
     */
    public function groupList(string $start = '0', string $limit = '50', ?string $q = null): array
    {
        // 必需参数校验
        if (!is_numeric($start)) {
            throw new EasyCVRException('开始位置必须是数字');
        }
        if (!is_numeric($limit)) {
            throw new EasyCVRException('每页数量必须是数字');
        }

        $query = [
            'start' => $start,
            'limit' => $limit,
        ];

        // 可选参数非null才加入
        if ($q !== null) {
            $query['q'] = $q;
        }

        return $this->request('get', '/api/v1/groups', [
            'query' => $query
        ]);
    }

    /**
     * 添加分组
     * @param string $name 分组名称
     * @param string $description 分组描述
     * @param int $parentId 父分组ID
     * @return array
     * @throws EasyCVRException
     */
    public function addGroup(string $name, string $description, int $parentId = 0): array
    {
        // 必需参数校验
        if (empty($name)) {
            throw new EasyCVRException('分组名称不能为空');
        }

        $data = [
            'name' => $name,
            'description' => $description,
            'parent_id' => $parentId
        ];

        return $this->request('postJson', '/api/v1/groups', $data);
    }

    /**
     * 编辑分组
     * @param int $groupId 分组ID
     * @param string $name 分组名称
     * @param string $description 分组描述
     * @param int $parentId 父分组ID
     * @return array
     * @throws EasyCVRException
     */
    public function editGroup(int $groupId, string $name, string $description, int $parentId = 0): array
    {
        // 必需参数校验
        if ($groupId <= 0) {
            throw new EasyCVRException('分组ID必须大于0');
        }
        if (empty($name)) {
            throw new EasyCVRException('分组名称不能为空');
        }

        $data = [
            'name' => $name,
            'description' => $description,
            'parent_id' => $parentId
        ];

        return $this->request('postJson', "/api/v1/groups/$groupId", $data);
    }

    /**
     * 删除分组
     * @param int $groupId 分组ID
     * @return array
     * @throws EasyCVRException
     */
    public function delGroup(int $groupId): array
    {
        // 必需参数校验
        if ($groupId <= 0) {
            throw new EasyCVRException('分组ID必须大于0');
        }

        return $this->request('postJson', "/api/v1/groups/$groupId");
    }

    /**
     * 编辑分组下设备
     * @param int $groupId 分组ID
     * @param array $deviceIds 设备ID列表
     * @return array
     * @throws EasyCVRException
     */
    public function editGroupDevices(int $groupId, array $deviceIds): array
    {
        // 必需参数校验
        if ($groupId <= 0) {
            throw new EasyCVRException('分组ID必须大于0');
        }

        return $this->request('postJson', "/api/v1/groups/$groupId/devices", [
            'device_ids' => $deviceIds
        ]);
    }

    /**
     * 编辑分组下通道
     * @param int $groupId 分组ID
     * @param array $channelIds 通道ID列表
     * @return array
     * @throws EasyCVRException
     */
    public function editGroupChannels(int $groupId, array $channelIds): array
    {
        // 必需参数校验
        if ($groupId <= 0) {
            throw new EasyCVRException('分组ID必须大于0');
        }

        return $this->request('postJson', "/api/v1/groups/$groupId/channels", [
            'channel_ids' => $channelIds
        ]);
    }

    /**
     * 共享分组节点
     * @param int $groupId 分组ID
     * @param array $userIds 用户ID列表
     * @return array
     * @throws EasyCVRException
     */
    public function shareGroup(int $groupId, array $userIds): array
    {
        // 必需参数校验
        if ($groupId <= 0) {
            throw new EasyCVRException('分组ID必须大于0');
        }

        return $this->request('postJson', "/api/v1/groups/$groupId/share", [
            'user_ids' => $userIds
        ]);
    }

    /**
     * 子分组列表
     * @param int $parentId 父分组ID
     * @param string $start 开始位置
     * @param string $limit 每页数量
     * @return array
     * @throws EasyCVRException
     */
    public function subGroupList(int $parentId, string $start = '0', string $limit = '50'): array
    {
        // 必需参数校验
        if ($parentId < 0) {
            throw new EasyCVRException('父分组ID不能小于0');
        }
        if (!is_numeric($start)) {
            throw new EasyCVRException('开始位置必须是数字');
        }
        if (!is_numeric($limit)) {
            throw new EasyCVRException('每页数量必须是数字');
        }

        return $this->request('get', "/api/v1/groups/$parentId/subgroups", [
            'query' => [
                'start' => $start,
                'limit' => $limit
            ]
        ]);
    }

    /**
     * 分组设备获取通道列表
     * @param int $groupId 分组ID
     * @param string $deviceId 设备ID
     * @return array
     * @throws EasyCVRException
     */
    public function groupDeviceChannels(int $groupId, string $deviceId): array
    {
        // 必需参数校验
        if ($groupId <= 0) {
            throw new EasyCVRException('分组ID必须大于0');
        }
        if (empty($deviceId)) {
            throw new EasyCVRException('设备ID不能为空');
        }

        return $this->request('get', "/api/v1/groups/$groupId/devices/$deviceId/channels");
    }

//-------------------------------------录像相关----------------------------------

    /**
     * 获取录像列表
     * @param string $id 通道ID
     * @param string $recordType 设备录像:Device,云端录像:Cloud
     * @param string $start 0
     * @param string $limit 9999
     * @param string $period 日期 20250423
     * @return array
     * @throws EasyCVRException
     */
    public function queryDaily(
        string $id,
        string $recordType,
        string $start,
        string $limit,
        string $period
    ): array
    {
        // 必需参数校验
        if (empty($id)) {
            throw new EasyCVRException('通道ID不能为空');
        }
        if (empty($recordType)) {
            throw new EasyCVRException('录像类型不能为空');
        }
        if (empty($period)) {
            throw new EasyCVRException('日期不能为空');
        }

        return $this->request('get', '/api/v1/record/querydaily', [
            'query' => [
                'id' => $id,
                'recordType' => $recordType,
                'start' => $start,
                'limit' => $limit,
                'period' => $period
            ]
        ]);
    }


    /**
     * 获取录像流
     * @param string $file 录像片段名称，=mnt=sd=2025-04-23=001=dav=00=00.00.00-00.30.00[R][0@0][0].dav
     * @param string $duration 录像片段时长，秒
     * @param string $sTime 录像片段开始时间，20250423000000
     * @param string $eTime 录像片段结束时间，20250423003000
     * @param string $channel 通道ID
     * @param string $protocol 拉流协议，flv
     * @return array
     * @throws EasyCVRException
     */
    public function playback(
        string $file,
        string $duration,
        string $sTime,
        string $eTime,
        string $channel,
        string $protocol
    ): array
    {
        // 必需参数校验
        if (empty($file)) {
            throw new EasyCVRException('录像片段名称不能为空');
        }
        if (empty($duration)) {
            throw new EasyCVRException('录像片段时长不能为空');
        }
        if (empty($sTime)) {
            throw new EasyCVRException('开始时间不能为空');
        }
        if (empty($eTime)) {
            throw new EasyCVRException('结束时间不能为空');
        }
        if (empty($channel)) {
            throw new EasyCVRException('通道ID不能为空');
        }
        if (empty($protocol)) {
            throw new EasyCVRException('拉流协议不能为空');
        }

        return $this->request('get', '/api/v1/devices/record/playback', [
            'query' => [
                'file' => $file,
                'duration' => $duration,
                'stime' => $sTime,
                'etime' => $eTime,
                'channel' => $channel,
                'protocol' => $protocol
            ]
        ]);
    }

    /**
     * 云端录像下载
     * @param string $id 通道ID
     * @param string $time 录像片段开始时间，20250423102240
     * @return array
     * @throws EasyCVRException
     */
    public function cloudRecordDownload(string $id, string $time): array
    {
        // 必需参数校验
        if (empty($id)) {
            throw new EasyCVRException('通道ID不能为空');
        }
        if (empty($time)) {
            throw new EasyCVRException('录像片段开始时间不能为空');
        }

        return $this->request('get', "/api/v1/record/download/$id/$time");
    }

    /**
     * 云端录像播放及下载
     * @param string $operate 调用操作 play:播放 download下载
     * 允许值: 文件类别 m3u8:(只提供在线地址) mp4:支持
     * 允许值: mp4, m3u8play, download
     * @param string $file 文件类别 m3u8:(只提供在线地址) mp4:支持
     * 允许值: mp4, m3u8
     * @param string $id 通道ID
     * @param string $startTime 录像片段开始时间，20250423102240
     * @param string $endTime 录像片段结束时间，20250423102240
     * @return array
     * @throws EasyCVRException
     */
    public function cloudRecordPlay(
        string $operate,
        string $file,
        string $id,
        string $startTime,
        string $endTime
    ): array
    {
        // 必需参数校验
        if (empty($operate)) {
            throw new EasyCVRException('操作类型不能为空');
        }
        if (empty($file)) {
            throw new EasyCVRException('文件类别不能为空');
        }
        if (empty($id)) {
            throw new EasyCVRException('通道ID不能为空');
        }
        if (empty($startTime)) {
            throw new EasyCVRException('开始时间不能为空');
        }
        if (empty($endTime)) {
            throw new EasyCVRException('结束时间不能为空');
        }

        return $this->request('get', "/api/v1/record/play/$operate/$file/$id/$startTime/$endTime");
    }

    /**
     * 设备录像下载
     * @param string $id 通道ID
     * @param string $filename 文件名称
     * @param string $duration 下载文件时长(GB、HKSDK按时间段默认可为0)
     * @param string $startTime 开始时间(GB、HKSDK、JT808、ehome支持), YYYYMMDDHHmmss
     * @param string $endTime 结束时间(GB、HKSDK、JT808、ehome支持), YYYYMMDDHHmmss
     * @param string $type 值为upload代表上传文件到ftp服务器；为progress时获取上传进度，返回文件大小和上传结果；为download时下载文件(仅JT808支持)
     * @param string|null $uid JT808协议接入时，type为progress和download时需要传入，数据为type=upload时返回的data值
     * @return array
     * @throws EasyCVRException
     */
    public function downloadDeviceRecord(
        string $id,
        string $filename,
        string $duration,
        string $startTime,
        string $endTime,
        string $type,
        ?string $uid = null
    ): array
    {
        // 必需参数校验
        if (empty($id)) {
            throw new EasyCVRException('通道ID不能为空');
        }
        if (empty($filename)) {
            throw new EasyCVRException('文件名称不能为空');
        }
        if (empty($duration)) {
            throw new EasyCVRException('下载文件时长不能为空');
        }
        if (empty($startTime)) {
            throw new EasyCVRException('开始时间不能为空');
        }
        if (empty($endTime)) {
            throw new EasyCVRException('结束时间不能为空');
        }
        if (empty($type)) {
            throw new EasyCVRException('类型不能为空');
        }

        $query = [
            'startTime' => $startTime,
            'endTime' => $endTime,
            'type' => $type,
        ];

        // 可选参数非null才加入
        if ($uid !== null) {
            $query['uid'] = $uid;
        }

        return $this->request('get', "/api/v1/record/downloaddevicerecord/$id/$filename/$duration", [
            'query' => $query
        ]);
    }

    /**
     * GB设备录像下载回调
     * @param string $streamid （下载设备录像文件接口返回值中 stream_id）
     * @return array
     * @throws EasyCVRException
     */
    public function streamInfo(string $streamid): array
    {
        // 必需参数校验
        if (empty($streamid)) {
            throw new EasyCVRException('streamid不能为空');
        }

        return $this->request('get', "/api/v1/record/streaminfo", [
            'query' => [
                'streamid' => $streamid
            ]
        ]);
    }

    /**
     * 设备录像倍速播放(GB)
     * @param int $channel 通道ID
     * @param string $url 流地址
     * @param string $command 事件 scale
     * @param int $scale 倍速0.5、1、2、4
     * @return array
     * @throws EasyCVRException
     */
    public function playbackScale(int $channel, string $url, string $command, int $scale): array
    {
        // 必需参数校验
        if ($channel <= 0) {
            throw new EasyCVRException('通道ID必须大于0');
        }
        if (empty($url)) {
            throw new EasyCVRException('流地址不能为空');
        }
        if (empty($command)) {
            throw new EasyCVRException('事件不能为空');
        }
        if (!in_array($scale, [0.5, 1, 2, 4])) {
            throw new EasyCVRException('倍速必须是0.5、1、2、4中的一个');
        }

        return $this->request('get', "/api/v1/devices/record/playback/control", [
            'query' => [
                'channel' => $channel,
                'url' => $url,
                'command' => $command,
                'scale' => $scale
            ]
        ]);
    }
}